﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;

namespace A2DFramework.PipelineEngine
{
    public class Pipeline<TIN, TOUT>
    {
        private Func<TIN, TOUT> method2Execute;
        private List<Action<TIN>> beforeExecuteActions = new List<Action<TIN>>();
        private List<Action<TIN, TOUT>> afterExecuteActions = new List<Action<TIN, TOUT>>();
        private Action<TIN, TOUT> finalAction;
        private List<Action<TIN, TOUT>> successActions = new List<Action<TIN, TOUT>>();
        private List<Action<TIN, TOUT>> failActions = new List<Action<TIN, TOUT>>();
        private bool transactionEnabled = false;

        public Pipeline(Func<TIN, TOUT> method)
        {
            this.method2Execute = method;
        }

        public Pipeline<TIN, TOUT> Before(Action<TIN> action)
        {
            beforeExecuteActions.Add(action);

            return this;
        }

        public Pipeline<TIN, TOUT> Next(Action<TIN, TOUT> action)
        {
            afterExecuteActions.Add(action);

            return this;
        }

        public Pipeline<TIN, TOUT> Finally(Action<TIN, TOUT> action)
        {
            this.finalAction = action;

            return this;
        }

        public Pipeline<TIN, TOUT> Successful(Action<TIN, TOUT> action)
        {
            successActions.Add(action);

            return this;
        }

        public Pipeline<TIN, TOUT> Exception(Action<TIN, TOUT> action)
        {
            failActions.Add(action);

            return this;
        }

        public Pipeline<TIN, TOUT> EnableTransaction()
        {
            this.transactionEnabled = true;

            return this;
        }

        public Pipeline<TIN, TOUT> DisableTransaction()
        {
            this.transactionEnabled = false;

            return this;
        }

        public PipelineResult<TOUT> Invoke(TIN argument)
        {
            PipelineResult<TOUT> result = new PipelineResult<TOUT>();

            if (this.transactionEnabled)
            {
                using (var ts = new TransactionScope())
                {
                    result = InvokeInner(argument, result);

                    ts.Complete();
                }
            }
            else
            {
                result = InvokeInner(argument, result);
            }

            return result;
        }

        private PipelineResult<TOUT> InvokeInner(TIN argument, PipelineResult<TOUT> result)
        {
            foreach (var action in this.beforeExecuteActions)
                action.Invoke(argument);

            try
            {
                result.Result = this.method2Execute.Invoke(argument);
                result.ExceptionExists = false;
                result.Exception = null;
            }
            catch (Exception ex)
            {
                result.ExceptionExists = true;
                result.Exception = ex;
            }

            foreach (var action in this.afterExecuteActions)
                action.Invoke(argument, result.Result);

            if (!result.ExceptionExists)
            {
                foreach (var action in this.successActions)
                    action.Invoke(argument, result.Result);
            }
            else
            {
                foreach (var action in this.failActions)
                    action.Invoke(argument, result.Result);
            }

            if (this.finalAction != null)
                this.finalAction.Invoke(argument, result.Result);
            return result;
        }
    }
}
